defmodule Pango.BaseDeeprImpl do
	@moduledoc """
	    ****************************************************************************************
	    *   启动成功App之后执行下实现该模式的md5函数,提前加载 special_object 里的模块            *
	    ****************************************************************************************
	    在编译的时候会从/lib/db 目录下获取所有的 PsqlSchema, Neo4jSchema定义了的模块 mod, 返回 %{mod.deepr_object => mod}(对象->模块)的对应关系
	    从/lib/mod_impl 目录下获取所有的 object_impl 模块,
	        把 "_impl" 替换成""变成 object, %{object => object_impl}(对象->增删改查模块)
	        Note: (所以最好是schema中定义的deepr_object + "impl"作为模块名;如果不这样, 则要自己在 impl_mods 里补充相关的 对象 -> mod_impl 的对应关系),
	    list, info 接口在解析的时候, 会判断 db 模块是否定义了 pipelines, 如: %{"list" => {mod, func, args1}}
	        如果有,则在解析 list, info 的参数的时候, 会先执行 mod.func(:before, user, args(用户传过来的此对象的参数), args1), 可以对用户的参数做一些限制
	        执行sql查询后,会执行 mod.func(:after, user, rows(查询结果), args1), 可以根据 id 得到具体内容(名称什么的)
	        如果没有相应的 pipeline, 则不会执行
	"""
	@doc """
	    deepr list 对象-repo 对应关系, 这里可以自己配置 deepr list, info 用的数据库配置
	    如果有多个数据库连接的时候可用(目前仅pgsql有该需求)
		%{
			# user对象 => {PsqlDb模块, query函数, [连接池名称]}
			"notice" => {PsqlDb, :query, []},
			"job_order" => {IotRepo, :query, []}
		},
		执行: apply(m, f, Enum.reverse([params, sql | a]))
		多个对象的 repo不一致会报错
	"""
	@callback get_repos() :: %{binary => {atom, atom, list}}

	@doc """
	默认repo,如果没配置对象的repo, 就会使用默认repo,如果为nil, 则会默认使用 Postgrex.query(:psql_pool, sql, params)
	"""
	@callback default_repo() :: atom | nil

	@doc """
	    %{对象 => mod}
	    deepr list 不能实现的,或者单独走数据库查询的放这
	    执行list的时候会先对象判断是否属于 special, 如果是,则把 对象转成 atom, 执行 apply(mod, object, args)
	"""
	@callback special_object() :: %{binary => atom}

	@doc """
	    deepr list 对象-模块 对应关系
	    /lib/db/ 没有对应的关系的时候,可以在这里写
	"""
	@callback list_mods() :: %{binary => atom}

	@doc """
	    deepr 增删改查 对象-模块的对应关系
	    /lib/mod_impl 没有对应的关系的时候,可以在这里写
	"""
	@callback impl_mods() :: %{binary => atom}

	defmacro __using__(_env) do
		quote unquote: false, location: :keep do
			@behaviour Pango.BaseDeeprImpl

			@doc """
			args: %{"对象" => %{
			    "()" => filter,
			    "=>" => select
			}}
			"""
			def list(%{"user" => user, "args" => args}) do
				{time, reply} = :timer.tc(Pango.DeeprList, :list, [user, args, __MODULE__, :list])
				Process.send(
					self(),
					{:statics_async, __MODULE__, "list>" <> Enum.join(Map.keys(args), ">"), time},
					[]
				)
				reply
			end

			def info(%{"user" => user, "args" => args}) do
				{time, reply} = :timer.tc(Pango.DeeprInfo, :info, [user, args, __MODULE__, :info])
				Process.send(
					self(),
					{:statics_async, __MODULE__, "info>" <> Enum.join(Map.keys(args), ">"), time},
					[]
				)
				reply
			end

			def insert(%{"user" => _user, "obj" => obj} = params) do
				write(obj, :insert, params)
			end

			def update(%{"user" => _user, "obj" => obj, "unique_key" => _unique_key} = params) do
				method = Map.get(params, "method")
				         |> StringLib.to_exists_atom()
				write(obj, method, params)
			end

			def delete(%{"user" => _user, "obj" => obj} = params) do
				write(obj, :delete, params)
			end


			@doc """
				insert, update, delete可用
				method参数: [user, params]
			"""
			@spec route(charlist, atom, list) :: any
			def route(obj, method, args) do
				mod = get_impl_mod(obj)
				{time, reply} = :timer.tc(mod, method, args)
				Process.send(self(), {:statics_async, mod, method, time}, [])
				reply
			end



			defp write(obj, method, params) do
				mod = get_impl_mod(obj)
				{time, reply} = :timer.tc(mod, method, [params])
				Process.send(self(), {:statics_async, mod, method, time}, [])
				reply
			end

			path = File.cwd! <> "/lib/db/"
			object_mapping =
				Pango.BaseDeeprImpl.get_db_mods(path, [], true)
				|> Enum.reduce(
					   %{},
					   fn (mod, acc) ->
						   object = mod.deepr_object()
						   Map.put(acc, object, mod)
					   end
				   )
			def map_db() do
				unquote(Macro.escape(object_mapping))
			end

			impls = File.cwd! <> "/lib/mod_impl/"
			        |> Pango.BaseDeeprImpl.get_impl_mods(%{})

			def map_impl() do
				unquote(Macro.escape(impls))
			end

			@doc """
			    deepr list, info接口, 获取对象是哪个模块
			"""
			def get_db_mod(object) do
				case Map.get(map_db(), object) do
					nil ->
						v = Map.get(__MODULE__.list_mods(), object)
						if v == nil do
							v = Map.get(__MODULE__.special_object(), object)
							v || PrivateCandy.debug_remind(__ENV__, "#{__MODULE__} 没有 db对象: #{object}")
						else
							v
						end
					mod ->
						mod
				end
			end

			@doc """
			    deepr 增删改接口, 该对象增删改的处理模块
			"""
			def get_impl_mod(object) do
				case Map.get(map_impl(), object) do
					nil ->
						v = Map.get(__MODULE__.impl_mods(), object)
						v || PrivateCandy.debug_remind(__ENV__, "#{__MODULE__}没有 impl对象: #{object}")
					mod ->
						mod
				end
			end

			def special_object() do
				%{}
			end

			def md5() do
				__MODULE__.special_object()
				|> Enum.each(&apply(elem(&1, 1), :module_info, [:md5]))
			end


			def list_mods() do
				%{}
			end

			def impl_mods() do
				%{}
			end

			def get_repos() do
				%{}
			end

			def default_repo() do
				{PsqlDb, :run, []}
			end

			defoverridable special_object: 0,
			               list_mods: 0,
			               impl_mods: 0,
			               get_repos: 0,
			               default_repo: 0,
			               list: 1,
			               insert: 1,
			               update: 1,
			               delete: 1,
			               info: 1,
			               map_impl: 0,
			               map_db: 0
		end
	end

	def get_db_mods(path, mods, all) do
		case File.ls(path) do
			{:ok, list} ->
				Enum.reduce(
					list,
					mods,
					fn dir, acc ->
						path = path <> "/" <> dir
						if File.dir?(path) do
							get_db_mods(path, acc, all)
						else
							mod = [Macro.camelize(Path.rootname(dir))]
							      |> Module.concat()
							mod.module_info(:md5)
							if :erlang.function_exported(mod, :db_type, 0) && (
								all || mod.db_type() == Pango.DbType.pg()) do
								[mod | acc]
							else
								acc
							end
						end

					end
				)
			_ ->
				[]
		end

	end

	def get_impl_mods(path, mods) do
		case File.ls(path) do
			{:ok, list} ->
				Enum.reduce(
					list,
					mods,
					fn dir, acc ->
						path = path <> "/" <> dir
						if File.dir?(path) do
							get_impl_mods(path, acc)
						else
							[filename | _] = String.split(dir, ".")
							if !String.contains?(filename, "deepr") do
								obj = String.replace(filename, "_impl", "")
								mod = [Macro.camelize(filename)]
								      |> Module.concat()
								Map.put(acc, obj, mod)
							else
								acc
							end
						end

					end
				)
			_ ->
				%{}
		end
	end


end
